package jsr292.java.lang.invoke;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import jsr292.weaver.Capabilities;
import jsr292.weaver.gen.TrampolineGenerator;
//import jsr292.weaver.sam.SAMGenerator;

public class MethodHandles {
    private MethodHandles() {
        // do not instantiate
    }
    
    public static Lookup lookup(Class<?> lookupClass) {
        return new Lookup(lookupClass,
                Lookup.PUBLIC | Lookup.PRIVATE | Lookup.PROTECTED | Lookup.PACKAGE);
    }
    
    public static Lookup publicLookup() {
        return Lookup.PUBLIC_LOOKUP;
    }


    public static final class Lookup {
        private final int lookupModes;
        private final Class<?> lookupClass;
        
        public static final int PUBLIC = 0x01;
        public static final int PRIVATE = 0x02;
        public static final int PROTECTED = 0x04;
        public static final int PACKAGE = 0x08;

        Lookup(Class<?> lookupClass, int lookupModes) {
            this.lookupClass = lookupClass;
            this.lookupModes = lookupModes;
        }
        
        public Class<?> lookupClass() {
            return (lookupClass == null)? Object.class: lookupClass;
        }
        
        public int lookupModes() {
            return lookupModes;
        }

        public Lookup in(Class<?> requestedLookupClass) {
            requestedLookupClass.getClass();   // nullcheck
            if (lookupClass == requestedLookupClass) {
                return this;
            }
            
            throw new UnsupportedOperationException("NYI");
        }

        
        static final Lookup PUBLIC_LOOKUP = new Lookup(null, PUBLIC);

        @Override
        public String toString() {
            return lookupClass().getName();
        }

        private static MethodHandle varargsMask(MethodHandle mh, int modifiers) {
            if ((modifiers & 0x00000080) != 0) { // varargs bit set ?
                MethodType type = mh.type();
                Class<?> lastParameterType = type.parameterType(type.parameterCount() - 1);
                return mh.asVarargsCollector(lastParameterType);
            }
            return mh;
        }
        
        public MethodHandle findStatic(Class<?> defc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
            Method method=lookupMethod(defc, name, type);
            int modifiers = method.getModifiers();
            if (!Modifier.isStatic(modifiers))
                throw new IllegalAccessException("method "+name+" of "+defc+" is not static");
            
            if (Capabilities.canDefine() && Access.isPublicVisible(modifiers, defc) && Access.isPublicVisible(type)) {
                return varargsMask(TrampolineGenerator.createATrampoline(defc, modifiers, name, type, true), modifiers);
            }
            return varargsMask(new MHLookupUnreflectMethod(method, type, true), modifiers);
        }
        
        private Method lookupMethod(Class<?> defc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
            Method method;
            if (lookupClass==null) {
        	if (!Modifier.isPublic(defc.getModifiers())) {
        	    throw new IllegalAccessException("declaring " + defc + " is not public");
        	}
        	
        	// security exceptions can be thrown here...
        	method=defc.getMethod(name, type.parameterTypes);    
        	   
            } else {
        	method = defaultLookupMethod(defc, name, type);
        	
        	// check access
        	if (!Access.isVisible(lookupClass, method))
        	    throw new IllegalAccessException("method "+method+" is not accessible from "+lookupClass);
            }
            
            // check return type
            if (!type.returnType.isAssignableFrom(method.getReturnType())) {
                throw new NoSuchMethodException("incompatible return type with "+method);
            }
            
            try { // just for performance
                method.setAccessible(true);
            } catch (SecurityException e) {
                // do nothing
            }
            return method;
        }
        
        private static Method defaultLookupMethod(Class<?> declaringClass, String name, MethodType type) throws NoSuchMethodException {
            Class<?>[] parameterTypes = type.parameterTypes;
            for(Class<?> clazz = declaringClass; clazz != null; clazz = clazz.getSuperclass()) {
        	try {
        	    return clazz.getDeclaredMethod(name, parameterTypes);
        	} catch (NoSuchMethodException e) { 
        	    // do nothing
        	} // security exceptions are thrown...
            }
            throw new NoSuchMethodException("no method "+name+" "+type+" accessible from "+declaringClass);
        }

        public MethodHandle findVirtual(Class<?> defc, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
            Method method=lookupMethod(defc, name, type);
            int modifiers = method.getModifiers();
            if (Modifier.isStatic(modifiers))
              throw new IllegalAccessException("method "+name+" of "+defc+" is static");
            
            MethodType methodType = type.insertParameterTypes(0, defc);
            if (Capabilities.canDefine() && Access.isPublicVisible(modifiers, defc) && Access.isPublicVisible(type)) {
                return varargsMask(TrampolineGenerator.createATrampoline(defc, modifiers, name, methodType, false), modifiers);
            }
            return varargsMask(new MHLookupUnreflectMethod(method, methodType, false), modifiers);
        }

        public MethodHandle findSpecial(Class<?> defc, String name, MethodType type,
                                        Class<?> specialCaller) throws NoSuchMethodException, IllegalAccessException {
            Method method=lookupMethod(defc, name, type);
            int modifiers = method.getModifiers();
            if (Modifier.isStatic(modifiers))
              throw new IllegalAccessException("method "+name+" of "+defc+" is static");
            
            // we can't fully support the semantics, but
            // if the caller is the declaring class and if the method is private, findSpecial is equivalent to findVirtual
            if (defc != specialCaller || !Modifier.isPrivate(modifiers)) {
                throw new UnsupportedOperationException();    
            }
            
            MethodType methodType = type.insertParameterTypes(0, defc);
            return varargsMask(new MHLookupUnreflectMethod(method, methodType, false), modifiers);
        }
        
        public MethodHandle findConstructor(Class<?> def, MethodType type) throws NoSuchMethodException, IllegalAccessException {
            if (type.returnType != void.class)
                throw new IllegalArgumentException("return type must be void");
            Constructor<?> constructor;
            if (lookupClass == null) {
                constructor = def.getConstructor(type.parameterTypes);
            } else {
                constructor = def.getDeclaredConstructor(type.parameterTypes);
                if (!Access.isVisible(lookupClass, constructor)) {
                    throw new IllegalAccessException("constructor "+constructor+" is not accessible from "+toString()+" access");
                }
                constructor = asAccessible(constructor);
            }
            
            return new MHLookupUnreflectConstructor(constructor, type.changeReturnType(def));
        }

        public MethodHandle bind(Object receiver, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
            Class<?> receiverClass = receiver.getClass();
            return insertArguments(findVirtual(receiverClass, name, type), 0, receiver);
        }

        public MethodHandle unreflect(Method m) throws IllegalAccessException {
            if (!m.isAccessible() && !Access.isVisible(lookupClass, m)) {
                throw new IllegalAccessException("method "+m+" is not accessible from "+toString()+" access");
            }
            
            m = asAccessible(m);
            
            int modifiers = m.getModifiers();
            boolean isStatic = Modifier.isStatic(modifiers);
            Class<?>[] parameterTypes = m.getParameterTypes();
            if (!isStatic) {
                Class<?>[] types = new Class<?>[1 + parameterTypes.length];
                types[0] = m.getDeclaringClass();
                System.arraycopy(parameterTypes, 0, types, 1, parameterTypes.length);
                parameterTypes = types;
            }
            MethodType type = MethodType.makeInternal(m.getReturnType(), true, parameterTypes);
            return varargsMask(new MHLookupUnreflectMethod(m, type, isStatic), modifiers);
        }
        
        private static Method asAccessible(Method method) throws IllegalAccessException {
            Class<?> declaringClass = method.getDeclaringClass();
            if (method.isAccessible() || Access.isPublicVisible(method.getModifiers(), declaringClass))
                return method;
            
            try {
                method = declaringClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
                method.setAccessible(true);
            } catch (SecurityException e) {
                throw (IllegalAccessException)new IllegalAccessException().initCause(e);
            } catch (NoSuchMethodException e) {
                throw new AssertionError(e);
            }
            return method;
        }

        public MethodHandle unreflectSpecial(Method m, Class<?> specialCaller) throws IllegalAccessException {
            if (!m.isAccessible() && !Access.isVisible(lookupClass, m)) {
                throw new IllegalAccessException("method "+m+" is not accessible from "+toString()+" access");
            }
            
            int modifiers = m.getModifiers();
            if (Modifier.isStatic(modifiers))
                throw new IllegalAccessException("method "+m.getName()+" of "+m.getDeclaringClass()+" is static");
            
            m = asAccessible(m);
            
            // we can't fully support the semantics, but
            // if the caller is the declaring class and if the method is private, findSpecial is equivalent to findVirtual
            if (m.getDeclaringClass() != specialCaller || !Modifier.isPrivate(modifiers)) {
                throw new UnsupportedOperationException();    
            }
            
            MethodType type = MethodType.makeInternal(m.getReturnType(), true, m.getParameterTypes());
            return varargsMask(new MHLookupUnreflectMethod(m, type, false), modifiers);
        }

        public MethodHandle unreflectConstructor(Constructor<?> constructor) throws IllegalAccessException {
            if (!constructor.isAccessible() && !Access.isVisible(lookupClass, constructor)) {
                throw new IllegalAccessException("constructor "+constructor+" is not accessible from "+toString()+" access");
            }
            
            constructor = asAccessible(constructor);
            
            MethodType type=MethodType.makeInternal(constructor.getDeclaringClass(), true, constructor.getParameterTypes());
            return varargsMask(new MHLookupUnreflectConstructor(constructor, type), constructor.getModifiers());
        }

        private static Constructor<?> asAccessible(Constructor<?> constructor ) throws IllegalAccessException {
            Class<?> declaringClass = constructor.getDeclaringClass();
            if (constructor.isAccessible() || Access.isPublicVisible(constructor.getModifiers(), declaringClass))
                return constructor;
            
            try {
                constructor = declaringClass.getDeclaredConstructor(constructor.getParameterTypes());
                constructor.setAccessible(true);
            } catch (SecurityException e) {
                throw (IllegalAccessException)new IllegalAccessException().initCause(e);
            } catch (NoSuchMethodException e) {
                throw new AssertionError(e);
            }
            return constructor;
        }
        
        public MethodHandle unreflectGetter(Field field) throws IllegalAccessException {
            if (!field.isAccessible() && !Access.isVisible(lookupClass, field)) {
                throw new IllegalAccessException("method "+field+" is not accessible from "+toString()+" access");
            } 
            
            field = asAccessible(field);
            
            MethodType type;
            if (Modifier.isStatic(field.getModifiers())) {
                type=MethodType.methodType(field.getType());
            } else {
                type=MethodType.makeInternal(field.getType(), true, field.getDeclaringClass());
            }
            return new MHLookupUnreflectGetter(field, type);
        }
        
        private static Field asAccessible(Field field ) throws IllegalAccessException {
            Class<?> declaringClass = field.getDeclaringClass();
            if (field.isAccessible() || Access.isPublicVisible(field.getModifiers(), declaringClass))
                return field;
            
            try {
                field = declaringClass.getDeclaredField(field.getName());
                field.setAccessible(true);
            } catch (SecurityException e) {
                throw (IllegalAccessException)new IllegalAccessException().initCause(e);
            } catch (NoSuchFieldException e) {
                throw new AssertionError(e);
            }
            return field;
        }
        
        public MethodHandle unreflectSetter(Field field) throws IllegalAccessException {
            if (!field.isAccessible() && !Access.isVisible(lookupClass, field)) {
                throw new IllegalAccessException("method "+field+" is not accessible from "+toString()+" access");
            }
            
            field = asAccessible(field);
            
            MethodType type;
            if (Modifier.isStatic(field.getModifiers())) {
                type=MethodType.makeInternal(void.class, true, field.getType());
            } else {
                type=MethodType.makeInternal(void.class, true, field.getDeclaringClass(), field.getType());
            }
            return new MHLookupUnreflectSetter(field, type);
        }
        
        private Field getField(Class<?> def, String fieldName, Class<?> type, boolean isStatic) throws NoSuchFieldException, IllegalAccessException {
            Field field;
            if (lookupClass == null) {
                field = def.getField(fieldName);
            } else {
                field = def.getDeclaredField(fieldName);
            } 
            
            if (field.getType() != type) {
                throw new NoSuchFieldException("wrong type, field "+fieldName+" is declared as "+field.getType()+" not "+type);
            }
            boolean fieldIsStatic = Modifier.isStatic(field.getModifiers());
            if (isStatic != fieldIsStatic) {
                throw new IllegalAccessException("field "+fieldName+ "is " + ((fieldIsStatic)?"static":"not static"));
            }
            return field;
        }
        
        public MethodHandle findGetter(Class<?> def, String fieldName, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
            Field field = getField(def, fieldName, type, false);
            return unreflectGetter(field);
        }
        
        public MethodHandle findSetter(Class<?> def, String fieldName, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
            Field field = getField(def, fieldName, type, false);
            return unreflectSetter(field);
        }
        
        public MethodHandle findStaticGetter(Class<?> def, String fieldName, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
            Field field = getField(def, fieldName, type, true);
            return unreflectGetter(field);
        }
        
        public MethodHandle findStaticSetter(Class<?> def, String fieldName, Class<?> type) throws NoSuchFieldException, IllegalAccessException {
            Field field = getField(def, fieldName, type, true);
            return unreflectSetter(field);
        }
    }


    public static MethodHandle arrayElementGetter(Class<?> arrayClass) throws IllegalArgumentException {
        if (!arrayClass.isArray())
            throw new IllegalArgumentException("not an array: "+arrayClass);
        return new MHArrayElementGetter(arrayClass);
    }

    public static MethodHandle arrayElementSetter(Class<?> arrayClass) throws IllegalArgumentException {
        if (!arrayClass.isArray())
            throw new IllegalArgumentException("not an array: "+arrayClass);
        return new MHArrayElementSetter(arrayClass);
    }

    public static MethodHandle exactInvoker(MethodType type) {
        return new MHExactInvoker(type.insertParameterTypes(0, MethodHandle.class));
    }
    
    public static MethodHandle genericInvoker(MethodType type) {
        return exactInvoker(type);
    }
    
    public static MethodHandle spreadInvoker(MethodType type, int objectArgCount) {
        if (objectArgCount < 0 || objectArgCount > type.parameterCount())
            throw new IllegalArgumentException("bad argument count "+objectArgCount);
        
        MethodHandle invoker = MethodHandles.genericInvoker(type);
        int spreadArgCount = type.parameterCount() - objectArgCount;
        return invoker.asSpreader(Object[].class, spreadArgCount);
    }
    
    public static
    MethodHandle explicitCastArguments(MethodHandle target, MethodType newType) {
        return target.asType(newType);
    }

    public static
    MethodHandle permuteArguments(MethodHandle target, MethodType newType, int[] reorder) {
        int length = reorder.length;
        MethodType targetType = target.type();
        if (targetType.parameterCount()!=length)
            throw new IllegalArgumentException("invalid reorder length "+length+" for target type "+targetType);
        
        int limit = newType.parameterCount();
        Class<?>[] types=new Class<?>[length];
        for(int i=0; i<length; i++) {
            int order = reorder[i];
            if (order<0  || order >= limit) {
                throw new IllegalArgumentException("order "+Arrays.toString(reorder)+" not in range at index "+i);
            }
            types[i] = newType.parameterType(order);
        }
        
        return new MHPermuter(
        	target.asType(MethodType.methodType(targetType.returnType(), types)),
        	newType,
        	reorder.clone());
    }

    public static MethodHandle insertArguments(MethodHandle target, int index, Object... values) {
        if (index<0 || index>target.type().parameterCount())
            throw new IllegalArgumentException("index out of range "+index);
        
        if (index != 0) {
          int[] orders = MHInserter.getInsertOrder(index, values.length, target.methodType.parameterCount());
          
          //FIXME should not use permuteArguments directly to avoid array cloning
          //      and automatic conversion
          target = permuteArguments(target, target.type().unpermuteOnInsert(index, values.length), orders);
        }
        
        return new MHInserter(target, values, target.type().dropParameterTypes(0, values.length));
    }

    public static
    MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
        return dropArguments(target, pos, Arrays.asList(valueTypes));
    }
    
    public static
    MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
        //FIXME use a more simple way to process the new MethodType
        int length=valueTypes.size();
        if (length == 0)  
            return target;
        MethodType oldType = target.type();
        int outargs = oldType.parameterCount();
        int inargs  = outargs + valueTypes.size();
        if (pos < 0 || pos >= inargs)
            throw new IllegalArgumentException("no argument type to remove");
        ArrayList<Class<?>> ptypes =
                new ArrayList<Class<?>>(oldType.parameterList());
        ptypes.addAll(pos, valueTypes);
        MethodType newType = MethodType.methodType(oldType.returnType(), ptypes);
        
        return new MHDropper(target, pos, length, newType);   
    }

    // check parameters and modify the test method handle to have the same
    // number of parameters as target and fallback.
    static MethodHandle checkGuardWithTest(MethodHandle test,
            final MethodHandle target,
            final MethodHandle fallback) {
        MethodType targetType = target.type();
        if (targetType != fallback.type())
            throw new IllegalArgumentException("target and fallback types do not match");
        
        MethodType testType = test.type();
        if (testType.returnType() != boolean.class)
            throw new IllegalArgumentException("test return type must be a boolean "+ testType);
        
        MethodType targetWithBooleanType = targetType.changeReturnType(boolean.class);
        if (testType != targetWithBooleanType) {
            int testTypeCount = testType.parameterCount();
            int targetTypeCount = targetType.parameterCount();
            if (testTypeCount > targetTypeCount)
                throw new IllegalArgumentException("test type has too much parameters"+ testType);
            
            test = dropArguments(test, testTypeCount, targetType.parameterList().subList(testTypeCount, targetTypeCount));
            if (test.type() != targetWithBooleanType)
                throw new IllegalArgumentException("target and test types"+ targetType + " "+ test.type());
        }
        return test;
    }
    
    public static MethodHandle guardWithTest(MethodHandle test,
                               MethodHandle target,
                               MethodHandle fallback) {
        test = checkGuardWithTest(test, target, fallback);
        return new MHGuarder(test, target, fallback);
    }
    
     public static
     MethodHandle catchException(MethodHandle target,
                                 Class<? extends Throwable> exType,
                                 MethodHandle handler) {
         MethodType handlerType = handler.type();
         MethodType targetType = target.type();
         if (targetType.parameterCount() != handlerType.parameterCount() - 1)
             throw new IllegalArgumentException("target type "+targetType+" and handler type "+ handlerType+" do not match");

         if (!handlerType.parameterType(0).isAssignableFrom(exType)) {
             throw new IllegalArgumentException("handler first parameter type is not compatible with the exception type");
         }

         // handler parameter types should be contravariant
         for(int i=0; i<targetType.parameterCount(); i++) {
             if (!handlerType.parameterType(i+1).isAssignableFrom(targetType.parameterType(i)))
                 throw new IllegalArgumentException("handler parameter types are not compatible with target types");
         }

         // handler parameter types should be covariant
         if (!targetType.returnType().isAssignableFrom(handlerType.returnType()))
             throw new IllegalArgumentException("handler return type is not compatible with target return type");


         return new MHCatcher(target, exType, handler);
     }
  
    
    public static
    MethodHandle throwException(Class<?> returnType, Class<? extends Throwable> exceptionType) {
        return new MHThrower(MethodType.methodType(returnType, exceptionType), exceptionType);
    }

    
    public static
    MethodHandle identity(Class<?> type) {
        return new MHIdentity(MethodType.methodType(type, type));
    }
    
    public static
    MethodHandle constant(Class<?> type, Object value) {
        return new MHConstant(MethodType.methodType(type), value);
    }
    
    public static MethodHandle filterArguments(MethodHandle target, int position, MethodHandle... filters) {
        MethodType targetType = target.type();                     // NPE target
        int length = targetType.parameterCount();
        int last = position + filters.length;
        if (position < 0 ||  last > length) {  // NPE filters
            throw new IllegalArgumentException("position < 0 || position + filters.length > target.type().parameterCount()");
        }
        
        if (length == 0) {  // shortcut
            return target;
        }
        
	MethodHandle[] filterMHs = new MethodHandle[length];
	Class<?>[] newParameterClasses = new Class<?>[length];
	boolean allNull = true;
	for(int i=position; i<last; i++) {
	    MethodHandle filter = filters[i - position];
	    if (filter == null) {
		newParameterClasses[i] = targetType.parameterType(i);
		continue;
	    }
	    
	    filterMHs[i] = filter;
	    allNull =  false;
	    MethodType filterType = filter.type();
            if (filterType.parameterCount() != 1 || 
                filterType.returnType() != targetType.parameterType(i))
                throw new IllegalArgumentException("target and filter types do not match");
            newParameterClasses[i] = filterType.parameterType(0);
	}
	
	if (allNull) {  // shortcut
	    return target;
	}
	
	for(int i=0; i<position; i++) {
            newParameterClasses[i] = targetType.parameterType(i);
        }
	for(int i=last; i<length; i++) {
	    newParameterClasses[i] = targetType.parameterType(i);
	}
	
	return new MHFilter(target, filterMHs,
		MethodType.methodType(targetType.returnType(), newParameterClasses));
    }
    
    public static MethodHandle filterReturnValue(MethodHandle target, MethodHandle filter) {
        MethodType targetType = target.methodType;
        MethodType filterType = filter.methodType;
        if (filterType.parameterCount() != 1 ||
            targetType.returnType != filterType.parameterTypes[0]) {
            throw new IllegalArgumentException("invalid filter type "+filter+" for target "+target);
        }
        
        return new MHFilterReturnValue(targetType.changeReturnType(filterType.returnType), target, filter);
    }
    
    
    public static
    MethodHandle foldArguments(MethodHandle target, MethodHandle combiner) {
	MethodType targetType = target.type();
        MethodType combinerType = combiner.type();
        
        if (targetType.parameterCount() < combinerType.parameterCount() + 1)
 	   throw new IllegalArgumentException("target type "+targetType+" and combiner type "+ combinerType+" do not match");
        
        if (!targetType.parameterType(0).isAssignableFrom(combinerType.returnType())) {
 	   throw new IllegalArgumentException("targetType first parameter type is not compatible with the combinerType return type");
        }
        
        // combiner parameter types should be contravariant
        for(int i=0; i<combinerType.parameterCount(); i++) {
 	   if (!combinerType.parameterType(i).isAssignableFrom(targetType.parameterType(i + 1)))
 	       throw new IllegalArgumentException("combiner parameter types are not compatible with target types");
        }
        
        return new MHFolder(target, combiner, target.type().dropParameterType(0));
    }
    
    
    public static
    <T> T asInstance(final MethodHandle target, Class<T> samType) {
          return samType.cast(Proxy.newProxyInstance(samType.getClassLoader(),
                  new Class<?>[]{ samType }, new SAMHandler(target, samType)));
    }
    
    public static
    boolean isWrapperInstance(Object o) {
        return getSAMHandler(o) != null;
    }
    
    public static
    MethodHandle wrapperInstanceTarget(Object o) {
        SAMHandler handler = getSAMHandler(o);
        if (handler == null) {
            throw new IllegalArgumentException("reference is not a SAM instance");
        }
        return handler.target;
    }
    
    public static
    java.lang.Class<?> wrapperInstanceType(Object o) {
        SAMHandler handler = getSAMHandler(o);
        if (handler == null) {
            throw new IllegalArgumentException("reference is not a SAM instance");
        }
        return handler.samType;
    }
    
    private static SAMHandler getSAMHandler(Object o) {
        if (o == null) {
            return null;
        }
        if (!Proxy.isProxyClass(o.getClass())) {
            return null;
        }
        InvocationHandler handler = Proxy.getInvocationHandler(o);
        if (!(handler instanceof SAMHandler)) {
            return null;
        }
        return (SAMHandler)handler;
    }
    
    private static final class SAMHandler implements InvocationHandler {
        final MethodHandle target;
        final Class<?> samType;

        SAMHandler(MethodHandle target, Class<?> samType) {
            this.target = target;
            this.samType = samType;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getDeclaringClass() == Object.class) {
                String name = method.getName();
                int parameterCount = method.getParameterTypes().length;
                if (parameterCount == 1) {
                    if ("equals".equals(name)) {
                        return proxy == args[0];
                    }
                } else if (parameterCount == 0) {
                    if ("hashCode".equals(name)) {
                        return System.identityHashCode(proxy);
                    }
                    if ("toString".equals(name)) {
                        return target.toString();
                    }
                }
            }
            return target.invokeWithArguments(args);
        }
    }
}
